/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtProcSession.h"
#include "WtUtil.h"
#include "WtTable.h"
#include "WtTableUtil.h"

Tcl_HashTable wtPSTables;
Tcl_Mutex wtPSTablesLock = NULL;

/* Get the session ID */

Tcl_Obj *WtGetProcSessionId(WtProcSession *sh, Tcl_Interp *interp)
{
  return sh->id;
}

/* Open the session */

int WtOpenProcSession(WtProcSession *sh, Tcl_Obj *id, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *curId;

  if (!Tcl_GetCharLength(id)) {
    Tcl_AppendResult(interp, "Session ID string is invalid.", NULL);
    ok = 0;
  } else {
    curId = WtGetProcSessionId(sh, interp);
    if (!curId || strcmp(WtToString(curId), WtToString(id))) {
      if (sh->psTable && !WtCloseProcSession(sh, interp)) {
        ok = 0;
      } else if (!WtOpenProcSessionTable(sh, id, interp)) {
        ok = 0;
      }
    }
  }

  return ok;
}

/* Close the session */

int WtCloseProcSession(WtProcSession *sh, Tcl_Interp *interp)
{
  int ok = 1;

  if (sh->psTable) {
    if (!WtCloseProcSessionTable(sh, interp)) {
      ok = 0;
    }
  }

  return ok;
}

/* Initialize the session */

int WtInitProcSession(WtProcSession *sh, Tcl_Interp *interp)
{
  int ok = 1;
  WtSession *s;
  Tcl_Obj *id;

  if (!sh->initDone) {
    if (!sh->psTable) {
      s = WtGetAssocSession(interp);
      if (!(id = WtGetInitialSessionId(s, NULL, interp))) {
        ok = 0;
      } else if (!WtOpenProcSession(sh, id, interp)) {
        ok = 0;
      } else {
        sh->initDone = 1;
      }
    }
  } else if (!sh->psTable) {
    Tcl_AppendResult(interp, "Session not open.", NULL);
    ok = 0;
  }

  return ok;
}

/* Open the session table storage */

int WtOpenProcSessionTable(WtProcSession *sh, Tcl_Obj *id,
    Tcl_Interp *interp)
{
  int ok = 1, created = 0;
  Tcl_HashEntry *ent;
  WtPSTable *st;

  if (sh->psTable && !WtCloseProcSessionTable(sh, interp)) {
    ok = 0;
  } else {
    Tcl_MutexLock(&wtPSTablesLock);
    ent = Tcl_CreateHashEntry(&wtPSTables,
      (char *)id, &created);
    if (!ent) {
      ok = 0;
    } else {
      if (!created) {
        sh->psTable = (Tcl_Obj *)Tcl_GetHashValue(ent);
        st = WtPSTableRep(sh->psTable);
      } else if (sh->psTable = WtNewPSTableObj()) {
        /*
        st = WtPSTableRep(sh->psTable);
        st->id = WtNewString(Tcl_GetString(id));
        Tcl_IncrRefCount(st->id);
        */
        Tcl_IncrRefCount(sh->psTable);
        Tcl_SetHashValue(ent, sh->psTable);
        sh->isNew = 1;
      }

      if (!sh->psTable) {
        ok = 0;
      } else {
        sh->id = WtNewString(Tcl_GetString(id));
        Tcl_IncrRefCount(sh->id);
        st = WtPSTableRep(sh->psTable);
        st->accessTime = time(NULL);
        Tcl_IncrRefCount(sh->psTable);
      }
    }
    Tcl_MutexUnlock(&wtPSTablesLock);
  }

  return ok;
}

/* Close the session table */

int WtCloseProcSessionTable(WtProcSession *sh, Tcl_Interp *interp)
{
  int ok = 1;

  if (sh->id || sh->psTable) {
    if (sh->id) {
      Tcl_DecrRefCount(sh->id);
      sh->id = NULL;
    }

    if (sh->psTable) {
      // WtPSTableRep(sh->psTable)->accessTime = time(NULL);
      Tcl_DecrRefCount(sh->psTable);
      sh->psTable = NULL;
    }

    sh->isNew = 0;
    sh->isModified = 0;
  }

  return ok;
}

/* Find a session table */

Tcl_Obj *WtGetProcSessionTable(Tcl_Obj *id)
{
  Tcl_Obj *psTable = NULL;
  Tcl_HashEntry *ent;

  Tcl_MutexLock(&wtPSTablesLock);
  ent = Tcl_FindHashEntry(&wtPSTables, (char *)id);
  if (ent) {
    psTable = (Tcl_Obj *)Tcl_GetHashValue(ent);
  }
  Tcl_MutexUnlock(&wtPSTablesLock);

  return psTable;
}

/* Status of a process session table */

WtSessionStatus WtGetPSTableStatus(Tcl_Obj *stObj, Tcl_Interp *interp)
{
  WtPSTable *st;
  WtSessionStatus status;
  WtSession *s;

  if (!stObj) {
    status = WT_SESSION_STATUS_INVALID;
  } else {
    st = WtPSTableRep(stObj);
    s = WtGetAssocSession(interp);
    if (time(NULL) - st->accessTime >= s->maxIdleTime) {
      status = WT_SESSION_STATUS_EXPIRED;
    } else {
      status = WT_SESSION_STATUS_ACTIVE;
    }
  }

  return status;
}

/* Delete a session */

int WtDeleteProcSession(WtProcSession *sh, Tcl_Obj *id, Tcl_Interp *interp)
{
  int ok = 1, deleteSelf = 0;
  Tcl_Obj *psTable;
  Tcl_HashEntry *ent;

  if (id) {
    if (!Tcl_GetCharLength(id)) {
      Tcl_AppendResult(interp, "Session ID string is invalid.", NULL);
      ok = 0;
    }
  } else if (!WtInitProcSession(sh, interp)) {
    ok = 0;
  } else if (!(id = WtGetProcSessionId(sh, interp))) {
    ok = 0;
  }

  if (ok) {
    Tcl_MutexLock(&wtPSTablesLock);
    ent = Tcl_FindHashEntry(&wtPSTables, (char *)id);
    if (ent) {
      psTable = (Tcl_Obj *)Tcl_GetHashValue(ent);
      if (sh->psTable == psTable) {
        deleteSelf = 1;
      }
      if (psTable) {
        Tcl_DecrRefCount(psTable);
      }
      Tcl_DeleteHashEntry(ent);
    }
    Tcl_MutexUnlock(&wtPSTablesLock);

    if (deleteSelf && !WtCloseProcSession(sh, interp)) {
      ok = 0;
    }
  }

  return ok;
}

/* Delete stale sessions */

int WtSweepProcSessions(WtProcSession *sh, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_HashSearch search;
  Tcl_HashEntry *ent;
  Tcl_Obj *val;
  WtPSTable *st;
  time_t maxTime;
  WtSession *s = WtGetAssocSession(interp);

  if (s->maxIdleTime > 0) {
    maxTime = time(NULL) + s->maxIdleTime;
    Tcl_MutexLock(&wtPSTablesLock);
    ent = Tcl_FirstHashEntry(&wtPSTables, &search);
    while (ent) {
      val = (Tcl_Obj *)Tcl_GetHashValue(ent);
      if (val) {
        st = WtPSTableRep(val);
        if (st->accessTime <= maxTime) {
          Tcl_DecrRefCount(val);
          Tcl_DeleteHashEntry(ent);
        }
      }
      ent = Tcl_NextHashEntry(&search);
    }
    Tcl_MutexUnlock(&wtPSTablesLock);
  }

  return ok;
}

/* Finalize request */

int WtProcSessionFinalizeRequest(WtProcSession *sh, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *id;
  WtSession *s = WtGetAssocSession(interp);

  if (!WtInitProcSession(sh, interp)) {
    ok = 0;
  } else if (id = WtGetProcSessionId(sh, interp)) {
    if (!WtSetSessionCookie(s, id, interp, s->clientTimeStamp)) {
      ok = 0;
    }
  }

  if (WtShouldSweepSessions(s, interp) &&
      !WtSweepProcSessions(sh, interp)) {
    ok = 0;
  }

  return ok;
}

/* Session command */

int WtProcSessionCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  WtSession *s = WtGetAssocSession(interp);
  WtProcSession *sh;

  /* Initialize the session */

  if (!s->procSession) {
    s->procSession = WtNewProcSessionObj();
  }
  sh = WtProcSessionRep(s->procSession);
  return WtProcSessionCmdHelper(clientData, interp,
    sh, objc, objv);
}

/* Session command helper */

int WtProcSessionCmdHelper(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  char *subCmd;

  if (objc < 2) {
    WtProcSessionCmdUsage(interp, objv[0]);
    return ret;
  }

  subCmd = Tcl_GetString(objv[1]);

  if (!strcmp(subCmd, "id")) {
    ret = WtProcSessionIdCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "count")) {
    ret = WtProcSessionCountCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "has")) {
    ret = WtProcSessionHasCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "get")) {
    ret = WtProcSessionGetCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "set")) {
    ret = WtProcSessionSetCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "setDefault")) {
    ret = WtProcSessionSetDefaultCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "keys")) {
    ret = WtProcSessionKeysCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "values")) {
    ret = WtProcSessionValuesCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "remove")) {
    ret = WtProcSessionRemoveCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "removeList")) {
    ret = WtProcSessionRemoveListCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "array")) {
    ret = WtProcSessionArrayCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "items")) {
    ret = WtProcSessionItemsCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "touch")) {
    ret = WtProcSessionTouchCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "clear")) {
    ret = WtProcSessionClearCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "isAccessed")) {
    ret = WtProcSessionIsAccessedCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "isModified")) {
    ret = WtProcSessionIsModifiedCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "status")) {
    ret = WtProcSessionStatusCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "properties")) {
    ret = WtProcSessionPropertiesCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "clientProperties")) {
    ret = WtProcSessionClientPropertiesCmd(clientData, interp, sh, objc,
      objv);
  } else if (!strcmp(subCmd, "delete")) {
    ret = WtProcSessionDeleteCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "sweep")) {
    ret = WtProcSessionSweepCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "unload")) {
    ret = WtProcSessionUnloadCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "loadModule")) {
    ret = WtProcSessionLoadModuleCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "unloadModule")) {
    ret = WtProcSessionUnloadModuleCmd(clientData, interp, sh, objc, objv);
  } else if (!strcmp(subCmd, "finalizeRequest")) {
    ret = WtProcSessionFinalizeCmd(clientData, interp, sh, objc, objv);
  } else {
    WtProcSessionCmdUsage(interp, objv[0]);
  }

  return ret;
}

/* Session command usage */

void WtProcSessionCmdUsage(Tcl_Interp *interp, Tcl_Obj *cmd)
{
  char *cmdStr = Tcl_GetString(cmd);

  Tcl_AppendResult(interp,
    wtBadUsagePrefix2,
    cmdStr, " id\n",
    cmdStr, " count\n",
    cmdStr, " has key\n",
    cmdStr, " get key ?defaultValue?\n",
    cmdStr, " set key value\n",
    cmdStr, " setDefault key defaultValue\n",
    cmdStr, " keys\n",
    cmdStr, " values\n",
    cmdStr, " remove key\n",
    cmdStr, " removeList keys\n",
    cmdStr, " array arrayName\n",
    cmdStr, " items\n",
    cmdStr, " touch\n",
    cmdStr, " clear\n",
    cmdStr, " isAccessed\n",
    cmdStr, " isModified\n",
    cmdStr, " status ?id?\n",
    cmdStr, " properties ?id?\n",
    cmdStr, " delete ?id?\n",
    cmdStr, " sweep\n",
    cmdStr, " unload\n",
    cmdStr, " loadModule\n",
    cmdStr, " unloadModule\n",
    cmdStr, " finalizeRequest\n",
    NULL);
}

/* id command */

int WtProcSessionIdCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *id;

  if (objc == 2) {
    if (WtInitProcSession(sh, interp) && 
        (id = WtGetProcSessionId(sh, interp))) {
      Tcl_SetObjResult(interp, id ? id : WtNewString(NULL));
      ret = TCL_OK;
    }
  } else if (objc == 3) {
    if (WtOpenProcSession(sh, objv[2], interp)) {
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* count command */

int WtProcSessionCountCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 2) {
    if (mt = WtGetPSStorage(sh, interp)) {
      Tcl_SetObjResult(interp, Tcl_NewIntObj(WtMtTableSize(mt)));
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* has command */

int WtProcSessionHasCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 3) {
    if (mt = WtGetPSStorage(sh, interp)) {
      Tcl_SetObjResult(interp,
        Tcl_NewBooleanObj(WtMtTableHas(mt, objv[2])));
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
  }

  return ret;
}

/* get command */

int WtProcSessionGetCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt, *obj;
  
  if (objc == 3 || objc == 4) {
    if (mt = WtGetPSStorage(sh, interp)) {
      obj = WtMtTableGet(mt, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      } else if (objc > 3) {
        Tcl_SetObjResult(interp, objv[3]);
        ret = TCL_OK;
      } else {
        Tcl_AppendResult(interp,
          "No entry with key \"", Tcl_GetString(objv[2]),
          "\" exists, and no default value was specified.", NULL);
      }
    }
  }

  return ret;
}

/* set command */

int WtProcSessionSetCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 4) {
    if (mt = WtGetPSStorage(sh, interp)) {
      WtMtTableSet(mt, objv[2], objv[3]);
      Tcl_SetObjResult(interp, objv[3]);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key value", NULL);
  }

  return ret;
}

/* setDefault command */

int WtProcSessionSetDefaultCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt, *obj;

  if (objc == 4) {
    if (mt = WtGetPSStorage(sh, interp)) {
      obj = WtMtTableGet(mt, objv[2]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
      } else {
        WtMtTableSet(mt, objv[2], objv[3]);
      }
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " key value", NULL);
  }

  return ret;
}

/* keys command */

int WtProcSessionKeysCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt, *keys;

  if (objc == 2) {
    if (mt = WtGetPSStorage(sh, interp)) {
      if (WtMtTableKeys(mt, &keys, interp)) {
        Tcl_SetObjResult(interp, keys);
        ret = TCL_OK;
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* values command */

int WtProcSessionValuesCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt, *vals;

  if (objc == 2) {
    if (mt = WtGetPSStorage(sh, interp)) {
      if (WtMtTableValues(mt, &vals, interp)) {
        Tcl_SetObjResult(interp, vals);
        ret = TCL_OK;
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* remove command */

int WtProcSessionRemoveCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 3) {
    if (mt = WtGetPSStorage(sh, interp)) {
      WtMtTableRemove(mt, objv[2]);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " key", NULL);
  }

  return ret;
}

/* removeList command */

int WtProcSessionRemoveListCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 3) {
    if (mt = WtGetPSStorage(sh, interp)) {
      if (WtMtTableRemoveList(mt, objv[2], interp)) {
        ret = TCL_OK;
      }
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " list", NULL);
  }

  return ret;
}

/* array command */

int WtProcSessionArrayCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 3) {
    if (mt = WtGetPSStorage(sh, interp)) {
      WtMtTableLock(mt);
      if (WtTableToArray(interp, WtMtTableRep(mt)->tbl, objv[2], 0)) {
        ret = TCL_OK;
      }
      WtMtTableUnlock(mt);
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " arrayName",
      NULL);
  }

  return ret;
}

/* items command */

int WtProcSessionItemsCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt, *tbl;

  if (objc == 2) {
    if (mt = WtGetPSStorage(sh, interp)) {
      WtMtTableLock(mt);
      tbl = Tcl_DuplicateObj(WtMtTableStorage(mt));
      Tcl_SetObjResult(interp, tbl);
      WtMtTableUnlock(mt);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* touch command */

int WtProcSessionTouchCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    if (WtInitProcSession(sh, interp) && sh->psTable) {
      WtPSTableRep(sh->psTable)->accessTime = time(NULL);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* clear command */

int WtProcSessionClearCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  Tcl_Obj *mt;

  if (objc == 2) {
    if (mt = WtGetPSStorage(sh, interp)) {
      WtMtTableClear(mt);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* isAccessed command */

int WtProcSessionIsAccessedCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj((int)sh->psTable));
    ret = TCL_OK;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* isModified command */

int WtProcSessionIsModifiedCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sh->isModified));
    ret = TCL_OK;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* status command
 *
 * Result values: new, active, expired, invalid.
 */

int WtProcSessionStatusCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_OK;
  Tcl_Obj *id = NULL, *st;
  WtSessionStatus status;

  if (objc == 2 || objc == 3) {
    if (objc == 3) {
      id = objv[2];
      st = WtGetProcSessionTable(id);
      status = WtGetPSTableStatus(st, interp);
    } else if (!WtInitProcSession(sh, interp)) {
      ret = TCL_ERROR;
    } else if (sh->isNew) {
      status = WT_SESSION_STATUS_NEW;
    } else {
      status = WtGetPSTableStatus(sh->psTable, interp);
    }

    if (ret == TCL_OK) {
      Tcl_SetObjResult(interp, WtNewString(wtSessionStatusNames[status]));
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]),
      " ?id?", NULL);
    ret = TCL_ERROR;
  }

  return ret;
}

/* properties command */

int WtProcSessionPropertiesCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, self = 0;
  Tcl_Obj *id = NULL, *stObj, *tbl;
  WtSessionStatus status;
  WtPSTable *st;
  WtSession *s;

  if (objc == 2 || objc == 3) {
    if (objc == 2) {
      if (WtInitProcSession(sh, interp)) {
        if (id = WtGetProcSessionId(sh, interp)) {
          self = 1;
        }
      }
    } else {
      id = objv[2];
    }

    if (id) {
      stObj = WtGetProcSessionTable(id);
      if (stObj) {
        st = WtPSTableRep(stObj);
        tbl = WtNewTableObj();
        WtTableSetStrToObj(tbl, "id", id);
        WtTableSetStrToLong(tbl, "accessTime", (long)st->accessTime);
        WtTableSetStrToLong(tbl, "idleTime",
          (long)(time(NULL) - st->accessTime));
        WtTableSetStrToInt(tbl, "size", WtMtTableSize(st->sharedTable));
      }

      if (self && sh->isNew) {
        status = WT_SESSION_STATUS_NEW;
      } else {
        status = WtGetPSTableStatus(stObj, interp);
      }
      WtTableSetStrToStr(tbl, "status", wtSessionStatusNames[status]);

      Tcl_SetObjResult(interp, tbl);
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?", NULL);
  }

  return ret;
}

/* clientProperties command */

int WtProcSessionClientPropertiesCmd(ClientData clientData,
    Tcl_Interp *interp, WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtSession *s;
  Tcl_Obj *tbl;

  if (objc == 2) {
    s = WtGetAssocSession(interp);
    tbl = WtNewTableObj();
    WtTableSetStrToObj(tbl, "clientValue", s->clientValue);
    WtTableSetStrToObj(tbl, "clientId", s->clientId ?
      s->clientId : WtNewString(NULL));
    WtTableSetStrToLong(tbl, "clientTimeStamp",
      (long)s->clientTimeStamp);
    Tcl_SetObjResult(interp, tbl);
    ret = TCL_OK;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* delete command */

int WtProcSessionDeleteCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2 || objc == 3) {
    if (WtDeleteProcSession(sh, (objc == 3 ? objv[2] : NULL),
        interp)) {
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?", NULL);
  }

  return ret;
}

/* sweep command */

int WtProcSessionSweepCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    if (WtSweepProcSessions(sh, interp)) {
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* unload command */

int WtProcSessionUnloadCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    if (!sh->psTable || WtCloseProcSession(sh, interp)) {
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }
}

/* load command */

int WtProcSessionLoadModuleCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  } else {
    /* Do nothing */
    ret = TCL_OK;
  }

  return ret;
}

/* unloadModule command */

int WtProcSessionUnloadModuleCmd(ClientData clientData, Tcl_Interp *interp,
    WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  WtContext *w;
  int ret = TCL_ERROR;

  if (objc == 2) {
    if (!sh->psTable || WtCloseProcSession(sh, interp)) {
      ret = TCL_OK;
    }
    w = WtGetAssocContext(interp);
    Tcl_DecrRefCount(w->web->session.procSession);
    w->web->session.procSession = NULL;
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* finalizeRequest command */

int WtProcSessionFinalizeCmd(ClientData clientData, Tcl_Interp *interp,
                           WtProcSession *sh, int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc == 2) {
    if (WtProcSessionFinalizeRequest(sh, interp)) {
      ret = TCL_OK;
    }
  } else {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      WtToString(objv[0]), " ", WtToString(objv[1]), NULL);
  }

  return ret;
}

/* Initialize the session and retrieve the stroage table */

Tcl_Obj *WtGetPSStorage(WtProcSession *sh, Tcl_Interp *interp)
{
  Tcl_Obj *mt = NULL;
  if (WtInitProcSession(sh, interp)) {
    mt = WtPSTableRep(sh->psTable)->sharedTable;
  }
  return mt;
}

/* Create session commands */

void WtInitProcSessionCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "::wt::session::procSession::procSessionHandler",
    WtProcSessionCmd, NULL, NULL);
}

/* Process session type */

/* Create */

Tcl_Obj *WtNewProcSessionObj()
{
  Tcl_Obj *obj;
  WtProcSession *sh = (WtProcSession *)ckalloc(sizeof(WtProcSession));

  sh->initDone = 0;
  sh->id = NULL;
  sh->psTable = NULL;
  sh->isNew = 0;
  sh->isModified = 0;

  obj = Tcl_NewObj();
  obj->internalRep.twoPtrValue.ptr1 = sh;
  obj->internalRep.twoPtrValue.ptr2 = NULL;
  obj->typePtr = &WtProcSessionType;
  Tcl_InvalidateStringRep(obj);
  return obj;
}

/* Copy */

static void WtProcSessionDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtProcSession *srcRep = WtProcSessionRep(src);
  WtProcSession *dstRep = (WtProcSession *)ckalloc(
    sizeof(WtProcSession));

  dstRep->initDone = srcRep->initDone;

  if (srcRep->id) {
    dstRep->id = srcRep->id;
    Tcl_IncrRefCount(dstRep->id);
  } else {
    dstRep->id = NULL;
  }

  if (srcRep->psTable) {
    dstRep->psTable = srcRep->psTable;
    Tcl_IncrRefCount(dstRep->psTable);
  } else {
    dstRep->psTable = NULL;
  }

  dstRep->isNew = srcRep->isNew;
  dstRep->isModified = srcRep->isModified;
  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->internalRep.twoPtrValue.ptr2 = NULL;
  dst->typePtr = &WtProcSessionType;
  Tcl_InvalidateStringRep(dst);
}

/* Update the string rep */

static void WtProcSessionUpdateString(Tcl_Obj *obj)
{
  WtUpdateProtectedObjString(obj);
}

/* Convert */

static int WtProcSessionSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetProtectedObjFromAny(interp, obj, &WtProcSessionType);
}

/* Free */

static void WtProcSessionFree(Tcl_Obj *obj)
{
  WtProcSession *sh = WtProcSessionRep(obj);
  if (sh->id) {
    Tcl_DecrRefCount(sh->id);
    sh->id = NULL;
  }
  if (sh->psTable) {
    Tcl_DecrRefCount(sh->psTable);
    sh->psTable = NULL;
  }
  ckfree((char *)(sh));
}

WtProcSession *WtProcSessionRep(Tcl_Obj *obj)
{
  return (WtProcSession *)obj->internalRep.twoPtrValue.ptr1;
}

/* Process session type */

struct Tcl_ObjType WtProcSessionType =
{
  "procSession",
  WtProcSessionFree,
  WtProcSessionDup,
  WtProcSessionUpdateString,
  WtProcSessionSetFromAny
};

/* Process session table type. This is the storage
   object associated with an in-process session. */

/* Create */

Tcl_Obj *WtNewPSTableObj()
{
  Tcl_Obj *obj;
  WtPSTable *st = (WtPSTable *)ckalloc(sizeof(WtPSTable));

  st->sharedTable = WtNewMtTableObj();
  st->accessTime = -1;

  obj = Tcl_NewObj();
  obj->internalRep.twoPtrValue.ptr1 = st;
  obj->internalRep.twoPtrValue.ptr2 = NULL;
  obj->typePtr = &WtPSTableType;
  Tcl_InvalidateStringRep(obj);
  return obj;
}

/* Copy */

static void WtPSTableDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtPSTable *srcRep = WtPSTableRep(src);
  WtPSTable *dstRep = (WtPSTable *)ckalloc(
    sizeof(WtPSTable));

  if (srcRep->sharedTable) {
    dstRep->sharedTable = Tcl_DuplicateObj(srcRep->sharedTable);
    Tcl_IncrRefCount(srcRep->sharedTable);
  } else {
    srcRep->sharedTable = NULL;
  }

  dstRep->accessTime = srcRep->accessTime;
  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->internalRep.twoPtrValue.ptr2 = NULL;
  dst->typePtr = &WtPSTableType;
  Tcl_InvalidateStringRep(dst);
}

/* Update the string rep */

static void WtPSTableUpdateString(Tcl_Obj *obj)
{
  WtUpdateProtectedObjString(obj);
}

/* Convert */

static int WtPSTableSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetProtectedObjFromAny(interp, obj, &WtPSTableType);
}

/* Free */

static void WtPSTableFree(Tcl_Obj *obj)
{
  WtPSTable *st = WtPSTableRep(obj);
  if (st->sharedTable) {
    Tcl_DecrRefCount(st->sharedTable);
  }
  ckfree((char *)(st));
}

WtPSTable *WtPSTableRep(Tcl_Obj *obj)
{
  return (WtPSTable *)obj->internalRep.twoPtrValue.ptr1;
}

/* Process session type */

struct Tcl_ObjType WtPSTableType =
{
  "procSessionTable",
  WtPSTableFree,
  WtPSTableDup,
  WtPSTableUpdateString,
  WtPSTableSetFromAny
};
